﻿using System;
using System.Collections.Generic;

using System.Net;
using System.Net.Sockets;

using AnB.Common;

namespace AnB.Net
{
    public class ConnectionBase : NetObject
    {
        public ConnectionBase(Socket socket, int headlen) : base(socket.RemoteEndPoint.ToString())
        {
            mSocket = socket;
            mSocket.NoDelay = true;
            mHead = new byte[headlen];
        }
        public ConnectionBase(Socket socket) : this(socket, HEAD_LEN)
        {
        }

        public const int HEAD_LEN = 4;

        private Socket mSocket;
        public Socket Socket
        {
            get
            {
                return mSocket;
            }
        }

        public delegate void OnStopAction(ConnectionBase connection);

        public OnStopAction OnStop;

        public void Start()
        {
            Receive();
        }

        public override void Stop()
        {
            base.Stop();
            if (OnStop != null)
            {
                OnStop(this);
            }
            mSocket.Close();
        }

        private byte[] mHead;
        private int mLen = -1;

        protected void Receive()
        {
            if (IsStop)
            {
                return;
            }

            byte[] buf;
            buf = mLen < 0 ? mHead : new byte[mLen];
            SocketError error;
            try
            {
                mSocket.BeginReceive(buf, 0, buf.Length, SocketFlags.None, out error, Received, buf);
            }
            catch (Exception e)
            {
                Log.Get().ErrorFormat("{0} BeginReceive error {1}\n{2}", Alias, e.Message, e.StackTrace);
                Stop();
                return;
            }
            if (error != SocketError.Success)
            {
                Log.Get().ErrorFormat("{0} BeginReceive error {1}", Alias, error);
                Stop();
            }
        }

        private void Received(IAsyncResult result)
        {
            var buf = (byte[])result.AsyncState;
            SocketError error;
            int received;
            try
            {
                received = mSocket.EndReceive(result, out error);
            }
            catch (Exception e)
            {
                Log.Get().ErrorFormat("{0} EndReceive error {1}\n{2}", Alias, e.Message, e.StackTrace);
                Stop();
                return;
            }
            if (error != SocketError.Success)
            {
                Log.Get().ErrorFormat("{0} EndReceive error {1}", Alias, error);
                Stop();
                return;
            }

            bool handle;
            if (mLen < 0)
            {
                int len =
                    (buf[0] << 24) +
                    (buf[1] << 16) +
                    (buf[2] <<  8) +
                    (buf[3]      ) ;
                handle = false;
                if (len < buf.Length)
                {
                    Log.Get().ErrorFormat("{0} Received head len 0X{1:X8} < min {2}", Alias, len, buf.Length);
                    Stop();
                    return;
                }
                long sizeLimit= HandleHead(buf, HEAD_LEN, buf.Length - HEAD_LEN, out mHeadState);
                if (sizeLimit < 0)
                {
                    Log.Get().ErrorFormat("{0} get size limit failed", Alias);
                    Stop();
                    return;
                }
                if (len > sizeLimit)
                {
                    Log.Get().ErrorFormat("{0} Received head len 0X{1:X8} > max 0X{2:X8}", Alias, len, sizeLimit);
                    Stop();
                    return;
                }

                mLen = len - buf.Length;
            }
            else
            {
                mLen = -1;
                handle = true;
            }

            object state = mHeadState;
            Receive();

            if (handle)
            {
                Log.Get().DebugFormat("{0} Receive {1} bytes", Alias, mHead.Length + buf.Length);
                HandleBody(buf, 0, buf.Length, state);
            }

        }

        private object mHeadState;
        protected virtual long HandleHead(byte[] head, int index, int count, out object state)
        {
            state = null;
            return 0;
        }
        protected virtual void HandleBody(byte[] body, int index, int count, object state)
        {
        }

        protected void SendLengthy(List<ArraySegment<byte>> segments)
        {
            int len = 0;
            foreach (var segment in segments)
            {
                len += segment.Count;
            }
            len += HEAD_LEN;
            Log.Get().DebugFormat("{0} Send {1} bytes", Alias, len);
            var headlen = new byte[HEAD_LEN];
            headlen[0] = (byte)((len >> 24) & 0xff);
            headlen[1] = (byte)((len >> 16) & 0xff);
            headlen[2] = (byte)((len >>  8) & 0xff);
            headlen[3] = (byte)((len      ) & 0xff);

            segments.Insert(0, new ArraySegment<byte>(headlen));
            Send(segments);
        }
        protected void Send(List<ArraySegment<byte>> segments)
        {
            SocketError error;
            try
            {
                mSocket.BeginSend(segments, SocketFlags.None, out error, Sent, mSocket);
            }
            catch (Exception e)
            {
                Log.Get().ErrorFormat("{0} BeginSend error {1}\n{2}", Alias, e.Message, e.StackTrace);
                Stop();
                return;
            }
            if (error != SocketError.Success)
            {
                Log.Get().ErrorFormat("{0} BeginSend error {1}", Alias, error);
                Stop();
            }
        }

        private void Sent(IAsyncResult result)
        {
            var socket = (Socket)result.AsyncState;
            SocketError error;
            try
            {
                socket.EndSend(result, out error);
            }
            catch (Exception e)
            {
                Log.Get().ErrorFormat("{0} EndSend error {1}\n{2}", Alias, e.Message, e.StackTrace);
                Stop();
                return;
            }
            if (error != SocketError.Success)
            {
                Log.Get().ErrorFormat("{0} EndSend error {1}", Alias, error);
                Stop();
            }
        }
        public void RawSendLengthy(byte[] segment)
        {
            var segments = new List<ArraySegment<byte>>();
            segments.Add(new ArraySegment<byte>(segment));
            SendLengthy(segments);
        }
        public void RawSend(byte[] segment)
        {
            var segments = new List<ArraySegment<byte>>();
            segments.Add(new ArraySegment<byte>(segment));
            Send(segments);
        }
    }
}
